package com.atwy.graph.uf;

import java.util.Arrays;

/**
 * @Author: 小王子火
 * @Date: 2023/2/8
 * UF的应用，动态连通性其实就是一种等价关系，具有「自反性」「传递性」和「对称性」
 */
public class TestUF {
    public static void main(String[] args) {
        TestUF testUF = new TestUF();
        String[] equations = new String[]{"a==b","b!=c","c==a"};
        boolean equationsPossible = testUF.equationsPossible(equations);
        System.out.println(Arrays.toString(equations)+"->"+equationsPossible);
        System.out.println("====================================");
        char[][] board = new char[][]{
                {'X','X','X','X','O'},
                {'X','X','X','O','X'},
                {'O','O','X','O','X'},
                {'X','O','X','X','X'}
        };
        testUF.solve(board);
        for (int i = 0; i < board.length; i++) {
            System.out.println(Arrays.toString(board[i]));
        }


    }

    /**
     * 给你一个数组equations，装着若干字符串表示的算式。
     * 每个算式equations[i]长度都是 4，而且只有这两种情况：a==b或者a!=b，其中a,b可以是任意小写字母。
     * 你写一个算法，如果equations中所有算式都不会互相冲突，返回 true，否则返回 false。
     *
     * 比如说，输入["a==b","b!=c","c==a"]，算法返回 false，因为这三个算式不可能同时正确。
     *
     * 再比如，输入["c==c","b==d","x!=z"]，算法返回 true，因为这三个算式并不会造成逻辑冲突。
     *
     * 我们前文说过，动态连通性其实就是一种等价关系，具有「自反性」「传递性」和「对称性」，
     * 其实==关系也是一种等价关系，具有这些性质。所以这个问题用 Union-Find 算法就很自然。
     *
     * 核心思想是，
     * 将equations中的算式根据==和!=分成两部分，先处理==算式，使得他们通过相等关系各自勾结成门派；
     * 然后处理!=算式，检查不等关系是否破坏了相等关系的连通性。
     * @param equations
     * @return
     */
    boolean equationsPossible(String[] equations){
        // 初始化26个字母
        UF_03 uf = new UF_03(26);
        for (String eq : equations){
            // 先让相等的字母形成连通分量
            if(eq.charAt(1) == '='){
                char x = eq.charAt(0);
                char y = eq.charAt(3);
                uf.union(x-'a',y-'a');
            }
        }
        // 检查不等关系是否打破相等关系的连通性
        for (String eq : equations){
            if(eq.charAt(1) == '!'){
                char x = eq.charAt(0);
                char y = eq.charAt(3);
                // 如果连通，说明x==y,则当前表达式打破了相等关系
                if(uf.connected(x-'a',y-'a')){
                       return false;
                }
            }
        }

        return true;
    }

    /**
     * 被围绕的区域：给你一个 M×N 的二维矩阵，其中包含字符X和O，让你找到矩阵中完全被X围住的O，并且把它们替换成X。
     *
     * 注意哦，必须是完全被围的O才能被换成X，也就是说边角上的O一定不会被围，进一步，与边角上的O相连的O也不会被X围四面，也不会被替换：
     * 解决这个问题的传统方法也不困难，先用 for 循环遍历棋盘的四边，用 DFS 算法把那些与边界相连的O换成一个特殊字符，比如#；然后再遍历整个棋盘，把剩下的O换成X，把#恢复成O。这样就能完成题目的要求，时间复杂度 O(MN)。
     *
     * 这个问题也可以用 Union-Find 算法解决，虽然实现复杂一些，甚至效率也略低，但这是使用 Union-Find 算法的通用思想，值得一学。
     *
     * 你可以把那些不需要被替换的O看成一个拥有独门绝技的门派，它们有一个共同祖师爷叫dummy，这些O和dummy互相连通，而那些需要被替换的O与dummy不连通。
     *
     * 首先要解决的是，根据我们的实现，Union-Find 底层用的是一维数组，构造函数需要传入这个数组的大小，而题目给的是一个二维棋盘。
     *
     * 这个很简单，二维坐标(x,y)可以转换成x * n + y这个数（m是棋盘的行数，n是棋盘的列数）。
     * 敲黑板，这是将二维坐标映射到一维的常用技巧。
     *
     * 其次，我们之前描述的「祖师爷」是虚构的，需要给他老人家留个位置。索引[0.. m*n-1]都是棋盘内坐标的一维映射，那就让这个虚拟的dummy节点占据索引m*n好了。
     * @param board
     */
    void solve(char[][] board) {
        if (board.length == 0) return;

        int m = board.length;
        int n = board[0].length;
        // 给 dummy 留一个额外位置
        UF uf = new UF(m * n + 1);
        int dummy = m * n;
        // 将首列和末列的 O 与 dummy 连通
        for (int i = 0; i < m; i++) {
            if (board[i][0] == 'O')
                uf.union(i * n, dummy);
            if (board[i][n - 1] == 'O')
                uf.union(i * n + n - 1, dummy);
        }
        // 将首行和末行的 O 与 dummy 连通
        for (int j = 0; j < n; j++) {
            if (board[0][j] == 'O')
                uf.union(j, dummy);
            if (board[m - 1][j] == 'O')
                uf.union(n * (m - 1) + j, dummy);
        }
        // 方向数组 d 是上下左右搜索的常用手法
        int[][] d = new int[][]{
                //行，列
                {1,0},// 下
                {0,1},// 右
                {0,-1},// 左
                {-1,0} // 上
        };

        for (int i = 1; i < m - 1; i++)
            for (int j = 1; j < n - 1; j++)
                if (board[i][j] == 'O')
                    // 将此 O 与上下左右的 O 连通
                    for (int k = 0; k < 4; k++) {
                        int x = i + d[k][0];
                        int y = j + d[k][1];
                        if (board[x][y] == 'O')
                            uf.union(x * n + y, i * n + j);
                    }
        // 所有不和 dummy 连通的 O，都要被替换
        for (int i = 1; i < m - 1; i++)
            for (int j = 1; j < n - 1; j++)
                if (!uf.connected(dummy, i * n + j))
                    board[i][j] = 'X';
    }
}
